package top.wilsonlv.jaguar.cloud.upms.service;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import top.wilsonlv.jaguar.basecrud.Assert;
import top.wilsonlv.jaguar.basecrud.BaseModel;
import top.wilsonlv.jaguar.cloud.upms.controller.vo.MenuVO;
import top.wilsonlv.jaguar.cloud.upms.controller.vo.ResourceServerVO;
import top.wilsonlv.jaguar.cloud.upms.entity.*;
import top.wilsonlv.jaguar.cloud.upms.sdk.enums.ClientType;
import top.wilsonlv.jaguar.cloud.upms.sdk.enums.MenuType;
import top.wilsonlv.jaguar.cloud.upms.sdk.feign.RemoteUserService;
import top.wilsonlv.jaguar.cloud.upms.sdk.vo.RoleVO;
import top.wilsonlv.jaguar.cloud.upms.sdk.vo.UserVO;
import top.wilsonlv.jaguar.commons.data.encryption.util.EncryptionUtil;
import top.wilsonlv.jaguar.commons.web.exception.impl.CheckedException;
import top.wilsonlv.jaguar.security.model.SecurityAuthority;
import top.wilsonlv.jaguar.security.model.SecurityUser;

import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author lvws
 * @since 2021/11/1
 */
@Service
@RequiredArgsConstructor
public class PersonalService {

    private final FaceService faceService;

    private final UserService userService;

    private final DeptService deptService;

    private final UserFaceService userFaceService;

    private final UserRoleService userRoleService;

    private final RoleMenuService roleMenuService;

    private final MenuService menuService;

    private final ResourceServerService resourceServerService;

    private final PasswordEncoder passwordEncoder;

    private final DeviceService deviceService;

    private final UserDeviceService userDeviceService;


    public SecurityUser loadSecurityUser(Long userId) {
        UserVO userVo = userService.getDetail(userId, true, true);
        if (userVo == null) {
            return null;
        }

        SecurityUser securityUser = new SecurityUser();
        securityUser.setId(userVo.getId());
        securityUser.setUsername(userVo.getUserAccount());
        securityUser.setNickName(userVo.getUserNickName());
        securityUser.setPhone(userVo.getUserPhone());
        securityUser.setEmail(userVo.getUserEmail());
        securityUser.setPassword(userVo.getUserPassword());
        securityUser.setAccountNonLocked(!userVo.getUserLocked());
        securityUser.setEnabled(userVo.getUserEnable());
        securityUser.setUserType(RemoteUserService.USER_TYPE);
        securityUser.setFaceFeature(userVo.getUserFaceFeature());
        securityUser.setDeptId(userVo.getUserDeptId());
        securityUser.setBuiltIn(userVo.getUserBuiltIn());

        boolean credentialsNoExpired = userVo.getUserBuiltIn() ||
                (userVo.getUserPasswordLastModifyTime() != null && userVo.getUserPasswordLastModifyTime().plusDays(90).isAfter(LocalDateTime.now()));
        securityUser.setCredentialsNonExpired(credentialsNoExpired);

        securityUser.setAuthorities(new HashSet<>(userVo.getPermissions().size()));
        for (String permission : userVo.getPermissions()) {
            securityUser.getAuthorities().add(new SecurityAuthority(permission));
        }

        Set<Long> grantedDeptIds = deptService.findChildrenIds(securityUser.getDeptId());
        grantedDeptIds.add(securityUser.getDeptId());
        securityUser.setGrantedDeptIds(grantedDeptIds);
        return securityUser;
    }

    @Transactional
    public SecurityUser loadUserByUsername(String principal) {
        User user = userService.unique(Wrappers.lambdaQuery(User.class)
                .select(BaseModel::getId)
                .eq(User::getUserAccount, principal).or()
                .eq(User::getUserPhone, principal).or()
                .eq(User::getUserEmail, principal));
        if (user == null) {
            return null;
        }

        return this.loadSecurityUser(user.getId());
    }

    @Transactional
    public SecurityUser[] loadUsersByDeviceUid(String deviceUid, Long userId) {
        Device device = deviceService.getByDeviceUid(deviceUid);
        if (device == null) {
            return new SecurityUser[0];
        }

        List<SecurityUser> securityUsers = new ArrayList<>();
        List<UserDevice> userDevices = userDeviceService.list(Wrappers.lambdaQuery(UserDevice.class)
                .eq(UserDevice::getDeviceId, device.getId())
                .eq(userId != null, UserDevice::getUserId, userId));
        for (UserDevice userDevice : userDevices) {
            securityUsers.add(this.loadSecurityUser(userDevice.getUserId()));
        }
        return securityUsers.toArray(new SecurityUser[0]);
    }

    @Transactional
    public List<MenuVO> getUserMenus(Long currentUserId, String serverId, ClientType clientType) {
        Set<Long> roleIds = userRoleService.listRoleByUserId(currentUserId)
                .stream().map(RoleVO::getId).collect(Collectors.toSet());
        if (CollectionUtils.isEmpty(roleIds)) {
            return Collections.emptyList();
        }

        List<RoleMenu> roleMenus = roleMenuService.list(Wrappers.lambdaQuery(RoleMenu.class)
                .in(RoleMenu::getRoleId, roleIds));
        Set<Long> menuIds = roleMenus.stream().map(RoleMenu::getMenuId).collect(Collectors.toSet());
        if (CollectionUtils.isEmpty(menuIds)) {
            return Collections.emptyList();
        }

        Set<Long> indexMenuIds = roleMenus.stream().filter(RoleMenu::getIndex).map(RoleMenu::getMenuId).collect(Collectors.toSet());
        ResourceServer resourceServer = resourceServerService.getByServerId(serverId);
        List<Menu> menus = menuService.list(Wrappers.lambdaQuery(Menu.class)
                .eq(Menu::getResourceServerId, resourceServer.getId())
                .like(clientType != null, Menu::getClientTypes, clientType)
                .in(BaseModel::getId, menuIds)
                .orderByAsc(Menu::getMenuOrder));
        return list2tree(0, menus, indexMenuIds);
    }

    private List<MenuVO> list2tree(long parentId, List<Menu> menus, Set<Long> indexMenuIds) {
        List<MenuVO> menuVos = new ArrayList<>();
        Iterator<Menu> iterator = menus.iterator();
        while (iterator.hasNext()) {
            Menu menu = iterator.next();
            if (menu.getParentId().equals(parentId)) {
                iterator.remove();
                MenuVO menuVO = menu.toVo(MenuVO.class);
                menuVO.setClientTypes(menu.getClientTypes().split(","));
                if (menuVO.getMenuType() == MenuType.MENU) {
                    menuVO.setIndex(indexMenuIds.contains(menuVO.getId()));
                }
                menuVos.add(menuVO);
            }
        }

        for (MenuVO menuVO : menuVos) {
            List<MenuVO> children = list2tree(menuVO.getId(), menus, indexMenuIds);
            menuVO.setChildren(children);
        }
        return menuVos;
    }

    @Transactional
    public List<ResourceServerVO> getUserResourceServers(Long currentUserId) {
        return resourceServerService.getUserResourceServers(currentUserId);
    }

    @Transactional
    public void modifyUserInfo(Long userId, String userPhone, String userEmail, String userNickName, String userFaceImage) {
        User user = new User();
        user.setId(userId);
        user.setUserPhone(userPhone);
        user.setUserEmail(userEmail);
        user.setUserNickName(userNickName);

        if (StringUtils.hasText(userPhone)) {
            User byPhone = userService.getByPhone(userPhone);
            Assert.duplicate(byPhone, user, "用户手机号");
        }

        if (StringUtils.hasText(userEmail)) {
            User byEmail = userService.getByEmail(userEmail);
            Assert.duplicate(byEmail, user, "用户邮箱");
        }

        userService.updateById(user);

        if (StringUtils.hasText(userFaceImage)) {
            String feature = faceService.face2Feature(userFaceImage);
            UserFace userFace = userFaceService.getByUserId(userId);
            if (userFace == null) {
                userFace = new UserFace();
            }
            userFace.setUserFaceImage(userFaceImage);
            userFace.setUserFaceFeature(feature);
            userFaceService.saveOrUpdate(userFace);
        }
    }

    @Transactional
    public void modifyPassword(Long userId, String oldPassword, String newPassword) {
        User user = userService.getById(userId);

        if (!passwordEncoder.matches(oldPassword, user.getUserPassword())) {
            throw new CheckedException("密码错误");
        }

        if (EncryptionUtil.passwordUnmatched(newPassword)) {
            throw new CheckedException("密码格式为包含数字，字母大小写的6-20位字符串！");
        }

        user = new User();
        user.setId(userId);
        user.setUserPassword(passwordEncoder.encode(newPassword));
        user.setUserPasswordLastModifyTime(LocalDateTime.now());
        userService.updateById(user);
    }


}
